#pragma once

class CVariable : public CIdentifier
{
	friend class CVariables;
	
	bool						m_bIsArray		: 1;
	bool						m_bIsGlobal		: 1;
	bool						m_bIsLocal		: 1;
	bool						m_bIsPredefined : 1;
	bool						m_bIsTimer		: 1;
	int							m_nInitLine;
	int							m_nVarIndex;
	const CTypeBase			*	m_pType;
	int							m_nArraySize;

	CVariable(const char *szName, int index, const CTypeBase * pType) : CIdentifier(szName)
	{
		m_pType = pType;
		m_bIsGlobal = pType->IsVar();
		m_bIsLocal = pType->IsLVar();
		m_bIsArray = pType->IsAnyArray();
		m_bIsPredefined = false;
		m_nInitLine = 0;
		m_nVarIndex = index;
	}
	
	inline void							IsPredefined(bool b)				{m_bIsPredefined = b;}
	inline void							IsTimer(bool b)						{m_bIsTimer = b;}

public:
	inline bool							IsArray()							{return m_bIsArray;}
	inline bool							IsGlobal()							{return m_bIsGlobal;}
	inline bool							IsLocal()							{return m_bIsLocal;}
	inline bool							IsPredefined()						{return m_bIsPredefined;}
	inline bool							IsTimer()							{return m_bIsTimer;}
	inline int							GetDeclarationLine()				{return m_nInitLine;}
	inline int							GetIndex()							{return m_nVarIndex;}
	inline const CTypeBase			*	GetType()							{return m_pType;}
	inline void							SetDeclarationLine(int i)			{m_nInitLine = i;}
	inline void							SetIndex(int i)						{m_nVarIndex = i;}

	int									GetArraySize()						{return m_bIsArray ? m_nArraySize : 1;}

	void SetArraySize(int nSize)
	{
		if(nSize) m_bIsArray = true;
		if(nSize > 0) m_nArraySize = nSize;
	}

	int			GetSize()
	{
		// Return the number of var indexes used by this var inc. arrays
		if(!IsPredefined())
		{
			int var_size = 1, array_size = IsArray() ? GetArraySize() : 0;
			if(GetType()->GetBasicType() == PT_TEXT_LABEL)
				var_size = 2;
			else if(GetType()->GetBasicType() == PT_STRING)
				var_size = 4;
			if(array_size) var_size *= array_size;
			return var_size;
		}
		return 0;
	}
};

class CVariables
{
	static CList<CVariable>				s_pVarList;
	static CLListEx<CVariable*>			s_VarList;
	static CLListEx<CVariable*>			s_LVarList;
	static CCatalogue<CVariable>		s_pVarCatalogue;

	static int							s_nNumVars;
	static int							s_nNumLVars;
	static CVariable				*	s_pLastFoundVar;
	static CVariable				*	s_pLastFoundLVar;
	
	static CVariable * Add(const char *szName, const CTypeBase * pType, int offset, int array_size, bool bAddToList)
	{
		if(!offset && !pType->IsLVar())
			offset = ((MAIN_VAR_BLOCK_OFFSET / 4) + s_nNumVars) * 4;

		if(array_size)
		{
			switch(pType->GetType())
			{
			case PT_VAR_INT: pType = CSyntax::GetType(TYPE_VAR_INT_ARRAY);
				break;
			case PT_VAR_FLOAT: pType = CSyntax::GetType(TYPE_VAR_FLOAT_ARRAY);
				break;
			case PT_VAR_TEXT_LABEL: pType = CSyntax::GetType(TYPE_VAR_TEXT_LABEL_ARRAY);
				break;
			case PT_VAR_STRING: pType = CSyntax::GetType(TYPE_VAR_STRING_ARRAY);
				break;
			case PT_LVAR_INT: pType = CSyntax::GetType(TYPE_LVAR_INT_ARRAY);
				break;
			case PT_LVAR_FLOAT: pType = CSyntax::GetType(TYPE_LVAR_FLOAT_ARRAY);
				break;
			case PT_LVAR_TEXT_LABEL: pType = CSyntax::GetType(TYPE_LVAR_TEXT_LABEL_ARRAY);
				break;
			case PT_LVAR_STRING: pType = CSyntax::GetType(TYPE_LVAR_STRING_ARRAY);
				break;
			}
		}
		
		auto pVariable = new CVariable(szName, offset, pType);
		
		if(bAddToList)
		{
			if(pVariable->IsLocal()) s_LVarList.Add(pVariable);
			else s_VarList.Add(pVariable);

			s_pVarList.Add(pVariable->GetHash(), pVariable);
		}

		// Get the occupation of the variable
		int var_size = 1;
		if(pType->IsTextLabel())
			var_size = 2;
		else if(pType->IsString())
			var_size = 4;

		pVariable->SetArraySize(array_size);

		if(array_size) pType->IsLVar() ? s_nNumLVars += array_size * var_size : s_nNumVars += array_size * var_size;
		else pType->IsLVar() ? s_nNumLVars += var_size : s_nNumVars += var_size;
		return pVariable;
	}
	
public:
	static bool LoadFromFile(const char *szFilePath);

	static CVariable * Add(const char *szName, const CTypeBase * pType, int offset=0, int array_size=0)
	{
		return Add(szName, pType, offset, array_size, false);
	}

	// Dont use me
	inline static CVariable * Get(int offset)
	{
		return nullptr;
	}

	static CVariable * GetByName(const char *szName)
	{
		DWORD hash = GenerateHash(szName);
		auto pVariable = s_pVarList.Get(hash);
		// JUUUSTTT in case of collision...
		while(pVariable && !pVariable->IsNamed(szName, hash))
			pVariable = s_pVarList.Next();
		return pVariable;
	}

	static void Reset()
	{
		s_VarList.Clear();
		s_pVarList.Clear();
		s_pVarCatalogue.Clear();
		s_nNumVars = 0;
	}
};